#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>

#define MAX_MORSE_CODE_LEN 10
#define CHAR_DELIMITER "   "
#define WORD_DELIMITER "       "
#define TRUE 1
#define FALSE 0

struct MorseCode {
	int ch;
	char code[MAX_MORSE_CODE_LEN];
};

struct MorseCode alpha_table[26] = {
    {'a', ".-"	 }, {'b', "-..." }, {'c', "-.-." }, {'d', "-.."  }, {'e', "."    },
    {'f', "..-." }, {'g', "--."  }, {'h', "...." }, {'i', ".."   }, {'j', ".---" },
    {'k', "-.-"	 }, {'l', ".-.." }, {'m', "--"   }, {'n', "-."   }, {'o', "---"  },
    {'p', ".--." }, {'q', "--.-" }, {'r', ".-."  }, {'s', "..."  }, {'t', "-"    },
    {'u', "..-"	 }, {'v', "...-" }, {'w', ".--"  }, {'x', "-..-" }, {'y', "-.--" },
    {'z', "--.." }
};

struct MorseCode num_table[10] = {
	{'0', "-----"}, {'1', ".----"}, {'2', "..---"}, {'3', "...--"}, {'4', "....-"},
    {'5', "....."}, {'6', "-...."}, {'7', "--..."}, {'8', "---.."}, {'9', "----."}
};

struct Node {
	struct MorseCode morse_code;
	struct Node *left;
	struct Node *right;
};

struct Node *BuildBST();
void FreeBST(struct Node *tree);
void PrintBST(struct Node *tree);
void BuildBSTNode(struct Node *node, int ch);
int AlphaToCode(int ch);
int NumToCode(int ch);
int CodeToAlpha(struct Node *tree, char *code);
int CodeToNum(char *code);
int NextChar(FILE *file);
int SearchCodeInTree(struct Node *node, char *buf, int pos);

int main(int argc, char **argv)
{
	FILE *input_file = NULL, *output_file = NULL;
	struct Node *tree = NULL;
	int ch = 0, next_ch = 0;
	int index = 0;
	char buf[MAX_MORSE_CODE_LEN];
	int pos = 0;
	int map_ch = 0;
	int space_num = 0;

	if (argc < 3) {
		fprintf(stderr, "Usage Error: ./MorseCode file_type[en|ms] file\n");
		exit(-1);
	}

	input_file = fopen(argv[2], "r");
	output_file = fopen("output.txt", "w");
	if (!input_file || !output_file) {
		fprintf(stderr, "open error\n");
		exit(-1);
	}


	if (strcmp(argv[1], "en") == 0) {
		while ((ch = fgetc(input_file)) != EOF) {
			if (isalpha(ch)) {
				index = AlphaToCode(ch);
				if (index < 0) {
					fprintf(stderr, "index error\n");
					exit(-1);
				}
				fputs(alpha_table[index].code, output_file);
				next_ch = NextChar(input_file);
				switch (next_ch) {
					case EOF: 
						break;

					case ' ':
						fputs(WORD_DELIMITER, output_file);
						break;

					case '\n':
						fputs("\n", output_file);
						break;

					default:
						fputs(CHAR_DELIMITER, output_file);
						break;

				}
			} else if (isdigit(ch)) {
				index = NumToCode(ch);
				if (index < 0) {
					fprintf(stderr, "index error\n");
					exit(-1);
				}
				fputs(num_table[index].code, output_file);
				next_ch = NextChar(input_file);
				switch (next_ch) {
					case EOF: 
						break;

					case ' ':
						fputs(WORD_DELIMITER, output_file);
						break;

					case '\n':
						fputs("\n", output_file);
						break;

					default:
						fputs(CHAR_DELIMITER, output_file);
						break;

				}
			} else {
				continue;
			}
		}
	} else if (strcmp(argv[1], "ms") == 0) {
		tree = BuildBST();

		while ((ch = fgetc(input_file)) != EOF) {
			switch (ch) {
				case '.':
				case '-':
					if (space_num == 7) 
						fputc(' ', output_file);

					space_num = 0;
					buf[pos++] = ch;
					break;

				case '\n':
				case ' ':
					if (ch == ' ')
						space_num++;

					buf[pos] = '\0';
					pos = 0;

					if (buf[0] != '\0') {
						map_ch = CodeToAlpha(tree, buf);
						if (map_ch == -1) 
							map_ch = CodeToNum(buf);
						if (map_ch == -1) {
							fprintf(stderr, "morse to en not mapping\n");
							exit(-1);
						}
						fputc(map_ch, output_file);
					}

					if (ch == '\n')
						fputc('\n', output_file);
					break;

				default:
					break;
			}
		}

		FreeBST(tree);
		tree = NULL;
	} else {
		fprintf(stderr, "Usage Error: ./MorseCode file_type[en|ms] file\n");
		exit(-1);
	}

	fclose(input_file);
	fclose(output_file);

	exit(0);
}

struct Node *BuildBST()
{
	struct Node *tree = (struct Node *) malloc(sizeof(struct Node));
	if (!tree) {
		fprintf(stderr, "malloc error\n");
		return NULL;
	}
	
	tree->morse_code.code[0] = '\0';

	BuildBSTNode(tree, '.');
	BuildBSTNode(tree, '-');

	return tree;
}

void BuildBSTNode(struct Node *node, int ch)
{
	char buf[MAX_MORSE_CODE_LEN];
	char *p = NULL;
	int matched_ch = -1;

	strncpy(buf, node->morse_code.code, MAX_MORSE_CODE_LEN);
	p = buf + strlen(buf);
	*p++ = ch; *p = '\0';

	for (int i = 0; i < 26; i++) {
		if (strcmp(alpha_table[i].code, buf) == 0) {
			matched_ch = alpha_table[i].ch;
			break;
		}
	}

	if (matched_ch == -1) {
		if (ch == '.') 
			node->left = NULL;
		else 
			node->right = NULL;

		return;
	}
	
	if (ch == '.') {
		node->left = (struct Node *) malloc(sizeof(struct Node));
		if (!(node->left)) {
			fprintf(stderr, "malloc error\n");
			exit(-1);
		}

		node->left->morse_code.ch = matched_ch;
		strncpy(node->left->morse_code.code, buf, MAX_MORSE_CODE_LEN);
	} else {
		node->right = (struct Node *) malloc(sizeof(struct Node));
		if (!(node->right)) {
			fprintf(stderr, "malloc error\n");
			exit(-1);
		}

		node->right->morse_code.ch = matched_ch;
		strncpy(node->right->morse_code.code, buf, MAX_MORSE_CODE_LEN);
	}

	if (node->left != NULL) {
		BuildBSTNode(node->left, '.');
		BuildBSTNode(node->left, '-');
	}
	if (node->right != NULL) {
		BuildBSTNode(node->right, '.');
		BuildBSTNode(node->right, '-');
	}
}

int AlphaToCode(int ch)
{
	int lower_ch = 0;

	lower_ch = tolower(ch);

	for (int i = 0; i < 26; i++) {
		if (alpha_table[i].ch == lower_ch) 
			return i;
	}

	return -1;
}

int NumToCode(int ch)
{
	return (ch  - '0');
}

int NextChar(FILE *file)
{
	int ch = 0;

	ch = fgetc(file);
	if (ch == EOF) return ch;

	fseek(file, -1, SEEK_CUR);

	return ch;
}

void FreeBST(struct Node *tree)
{
	if (!tree) return;

	FreeBST(tree->left);
	FreeBST(tree->right);

	free(tree);
}

int CodeToAlpha(struct Node *tree, char *code)
{
	return SearchCodeInTree(tree, code, 0);
}

int CodeToNum(char *code)
{
	for (int i = 0; i < 10; i++) {
		if (strcmp(code, num_table[i].code) == 0) 
			return i + '0';
	}
	return -1;
}

int SearchCodeInTree(struct Node *node, char *buf, int pos)
{
	if (node == NULL) return -1;

	if (buf[pos] == '\0')
		return node->morse_code.ch;

	if (buf[pos++] == '.')
		return SearchCodeInTree(node->left, buf, pos);
	else 
		return SearchCodeInTree(node->right, buf, pos);
}

void PrintBST(struct Node *tree)
{
	if (tree == NULL) return;

	printf("%c ", tree->morse_code.ch);

	PrintBST(tree->left);
	PrintBST(tree->right);
}